package org.alicuu.ioc.reader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by IntelliJ IDEA.
 * User: Danny
 * Date: 16/07/05
 * Time: 09:42
 */
public class JarClassReader extends AbstractClassReader implements ClassReader {
    private static final Logger log = LoggerFactory.getLogger(JarClassReader.class);

    @Override
    public Set<ClassInfo> getClass(String packageName, boolean recursive) {
        return getClassByAnnotation(packageName, null, null, recursive);
    }

    @Override
    public Set<ClassInfo> getClass(String packageNamw, Class<?> parent, boolean recursive) {
        return getClassByAnnotation(packageNamw, parent, null, recursive);
    }

    @Override
    public Set<ClassInfo> getClassByAnnotation(String packageName, Class<? extends Annotation> annotation, boolean recursive) {
        return this.getClassByAnnotation(packageName, null, annotation, recursive);
    }

    @Override
    public Set<ClassInfo> getClassByAnnotation(String packageName, Class<?> parent, Class<? extends Annotation> annotation, boolean recursive) {
        Set<ClassInfo> classes = new HashSet<>();
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs;
        try {
            dirs = this.getClass().getClassLoader().getResources(packageName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                Set<ClassInfo> subClasses = this.getClasses(url, packageDirName, packageName, parent, annotation, recursive, classes);
                if (subClasses.size() > 0)
                    classes.addAll(subClasses);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

    private Set<ClassInfo> getClasses(final URL url, final String packageDirName, String packageName, final Class<?> parent,
                                      final Class<? extends Annotation> annotation, final boolean recursive, Set<ClassInfo> classes) {
        try {
            if (url.toString().startsWith("jar:file") || url.toString().startsWith("wsjar:file")) {
                JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                Enumeration<JarEntry> jarEntryEnums = jarFile.entries();
                while (jarEntryEnums.hasMoreElements()) {
                    JarEntry entry = jarEntryEnums.nextElement();
                    String name = entry.getName();
                    if (name.charAt(0) == '/')
                        name = name.substring(1);
                    if (name.startsWith(packageDirName)) {
                        int index = name.lastIndexOf('/');
                        if (index != -1)
                            packageName = name.substring(0, index).replace('/', '.');
                        if ((index != -1) || recursive) {
                            if (name.endsWith(".class") && !entry.isDirectory()) {
                                String className = name.substring(packageName.length() + 1, name.length() - 6);
                                Class<?> clazz = Class.forName(packageName + "." + className);
                                if (null != parent && null != annotation) {
                                    if (null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent) && null != clazz.getAnnotation(annotation))
                                        classes.add(new ClassInfo(clazz));
                                    continue;
                                }
                                if (null != parent) {
                                    if (null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent))
                                        classes.add(new ClassInfo(clazz));
                                    continue;
                                }
                                if (null != annotation) {
                                    if (null != clazz.getAnnotation(annotation))
                                        classes.add(new ClassInfo(clazz));
                                    continue;
                                }
                                classes.add(new ClassInfo(clazz));
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return classes;
    }
}
